﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using Furion;
using Furion.DatabaseAccessor;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;

namespace YShop.Core
{
    public class DatabaseManager
    {
        private Type _dbContextType { get; set; }
        public DbContext _dbContext { get; set; }
        private IFreeSql _freeSql { get; set; }

        public DatabaseManager()
        {
            Build();
        }
        /// <summary>
        /// 构建FreeSql
        /// </summary>
        /// <returns></returns>
        public IFreeSql Build()
        {
            _dbContextType = App.EffectiveTypes.FirstOrDefault(t => typeof(DbContext).IsAssignableFrom(t));
            var connectionStringPath = _dbContextType.GetCustomAttribute<AppDbContextAttribute>(true).ConnectionString;
            _freeSql = new FreeSql.FreeSqlBuilder()
            .UseConnectionString(FreeSql.DataType.MySql, App.Configuration[connectionStringPath])
            .Build();
            if (App.WebHostEnvironment.IsDevelopment())
            {
                try
                {
                    if (Boolean.Parse(App.Configuration["DatabaseSettings:AutoSyncDataStructure"] ?? "false"))
                        AutoSyncDataStructure();

                    if (Boolean.Parse(App.Configuration["DatabaseSettings:ReloadSeedData"] ?? "false"))
                        ReloadSeedData();

                }
                catch (Exception ex)
                {
                    throw;
                }

            }
            return _freeSql;
        }
        #region 同步数据库结构

        public void AutoSyncDataStructure()
        {
            Console.WriteLine($"开始同步表结构...");
            var entityCorrelationTypes = App.EffectiveTypes.Where(t => typeof(IEntity).IsAssignableFrom(t)
                && t.IsClass && !t.IsAbstract && !t.IsGenericType && !t.IsInterface && !t.IsDefined(typeof(NonAutomaticAttribute), true))
                .ToList();
            foreach (var entityType in entityCorrelationTypes)
            {
                var tableInfo = _freeSql.CodeFirst.GetTableByEntity(entityType);
                var dbTableInfo = _freeSql.DbFirst.GetTableByName(tableInfo.DbName);
                var ignoreList = new List<string>();
                Console.WriteLine($"正在兼容EF Core配置:[{entityType.Name}],表名:[{tableInfo.DbName}]");

                // 配置
                _freeSql.CodeFirst.Entity(entityType, builder =>
                {
                    // 种子数据
                    //var hasDataMethod = entityType.GetMethod("HasData");
                    //if (hasDataMethod != null)
                    //{
                    //    var seedData = ((IList)hasDataMethod.Invoke(Activator.CreateInstance(entityType), new object[] { null, null })).Cast<object>().ToList();
                    //    builder.HasData(seedData);
                    //}

                    foreach (var property in entityType.GetProperties())
                    {
                        Type ptype = property.PropertyType;
                        // 可空类型处理
                        if (Utils.IsNullable(property.PropertyType))
                            ptype = Nullable.GetUnderlyingType(property.PropertyType);
                        // 枚举类型列处理
                        if (ptype.IsEnum)
                            builder.Property(property.Name).HasColumnType("int");
                        // 集合处理
                        if (ptype.IsArrayOrList())
                        {
                            // 一对多
                            var listType = ptype;
                            ptype = ptype.GetGenericArguments()[0];

                            if (ptype.GetInterface("IEntity") != null)
                            {
                                builder.HasMany(property.Name).HasForeignKey($"{entityType.Name}Id");
                            }
                        }
                        else
                        {
                            // 一对一
                            if (ptype.Equals(typeof(IEntity)))
                            {
                                builder.HasOne(ptype.Name).HasForeignKey($"{ptype}Id");
                            }
                        }
                        
                    }

                });

            }
            var ddl = _freeSql.CodeFirst.GetComparisonDDLStatements(entityCorrelationTypes.ToArray());
            _freeSql.Ado.ExecuteNonQuery(CommandType.Text, ddl);


            Console.WriteLine($"完成同步表结构...");
        }
        /// <summary>
        /// 生成删除列的SQL语句
        /// </summary>
        /// <param name="dbType"></param>
        /// <param name="tableName"></param>
        /// <param name="columnNames"></param>
        /// <returns></returns>
        private string GenerateDropColumnDDL(FreeSql.DataType dbType, string tableName, params string[] columnNames)
        {
            var sql = new StringBuilder();
            switch (dbType)
            {
                case FreeSql.DataType.MySql:
                    sql.AppendLine($"ALTER TABLE {tableName}");
                    sql.AppendLine(string.Join(",\r\n", columnNames.Select(col => $"DROP COLUMN {col}")))
                        .Append(';');
                    break;
                case FreeSql.DataType.SqlServer:
                    sql.AppendLine($"ALTER TABLE {tableName}");
                    sql.AppendLine($"DROP ");
                    sql.Append(string.Join(",", columnNames))
                        .Append(';');
                    break;
                case FreeSql.DataType.Sqlite: // FreeSql的做法是 创建临时表，数据导进临时表，然后删除原表，将临时表改名为原表名
                    break;
                case FreeSql.DataType.Oracle:
                    sql.AppendLine($"ALTER TABLE {tableName} DROP ({string.Join(",", columnNames)})");
                    break;
            }
            return sql.ToString();
        }

        private string GenerateAlterColumnDDL(FreeSql.DataType dbType, string tableName, params string[] columnNames)
        {
            var sql = new StringBuilder();
            return sql.ToString();
        }
        #endregion
        /// <summary>
        /// 配置种子数据
        /// </summary>
        public void ReloadSeedData()
        {
            Console.WriteLine($"开始加载种子数据...");
            // 加载种子配置数据
            var entityCorrelationTypes = App.EffectiveTypes.Where(t => typeof(IEntity).IsAssignableFrom(t)
                && t.IsClass && !t.IsAbstract && !t.IsGenericType && !t.IsInterface && !t.IsDefined(typeof(NonAutomaticAttribute), true))
                .ToList();
            var insertSqlList = new List<string>();
            foreach (var entitySeedDataType in entityCorrelationTypes)
            {
                var hasDataMethod = entitySeedDataType.GetMethod("HasData");
                if (hasDataMethod != null)
                {
                    _freeSql.Ado.ExecuteNonQuery(CommandType.Text, $"TRUNCATE TABLE {(entitySeedDataType.GetCustomAttribute<TableAttribute>(true)?.Name ?? entitySeedDataType.Name)}");
                    var instance = Activator.CreateInstance(entitySeedDataType);
                    var seedData = ((IList)hasDataMethod.Invoke(instance, new object[] { null, null })).Cast<object>().ToList();
                    insertSqlList.Add(GenerateInsertSql(entitySeedDataType, seedData, _freeSql.Ado.DataType));
                }
            }
            _freeSql.Ado.ExecuteNonQuery(CommandType.Text, string.Join("\r\n", insertSqlList));
            Console.WriteLine($"完成加载种子数据...");
        }
        public string GenerateInsertSql(Type entitySeedDataType, List<object> data, FreeSql.DataType dbType)
        {
            var sql = new StringBuilder();
            var tableName = (entitySeedDataType.GetCustomAttribute<TableAttribute>(true)?.Name ?? entitySeedDataType.Name);
            var parperties = entitySeedDataType.GetProperties();

            foreach (var item in data)
            {
                switch (dbType)
                {
                    case FreeSql.DataType.MySql:
                        sql.AppendLine($"INSERT INTO {tableName} ({GeneratePropertyNameJoinSql(parperties, dbType)}) VALUES ({GeneratePropertyValueJoinSql(entitySeedDataType, parperties, item, dbType)});");
                        break;
                    case FreeSql.DataType.SqlServer:
                        break;
                    case FreeSql.DataType.Sqlite:
                        break;
                    case FreeSql.DataType.Oracle:
                        break;
                }
            }
            return sql.ToString();
        }

        private string GeneratePropertyValueJoinSql(Type entitySeedDataType, PropertyInfo[] parperties, object item, FreeSql.DataType dbType)
        {
            var result = new List<string>();
            var columns = _freeSql.DbFirst.GetTableByName((entitySeedDataType.GetCustomAttribute<TableAttribute>(true)?.Name ?? entitySeedDataType.Name)).Columns;
            foreach (var property in parperties)
            {
                if (!property.PropertyType.IsArrayOrList() && property.PropertyType.GetInterface("IEntity") == null)
                {
                    var val = property.GetValue(item);
                    result.Add(FormatValueTypeMySql(columns.Find(c => c.Name == property.Name).DbTypeText, val));
                }
            }
            return string.Join(",", result);
        }
        /// <summary>
        /// 属性名连接
        /// </summary>
        /// <param name="parperties"></param>
        /// <param name="dbType"></param>
        /// <returns></returns>
        public string GeneratePropertyNameJoinSql(PropertyInfo[] parperties, FreeSql.DataType dbType)
        {
            return dbType switch
            {
                FreeSql.DataType.MySql => string.Join(",", parperties.Where(p => !p.PropertyType.IsArrayOrList() && p.PropertyType.GetInterface("IEntity") == null).Select(s => s.Name)),
                FreeSql.DataType.SqlServer => "",
                FreeSql.DataType.Sqlite => "",
                FreeSql.DataType.Oracle => "",
                _ => "",
            };
        }
        /// <summary>
        /// 格式化值类型
        /// </summary>
        /// <param name="type"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        private string FormatValueTypeMySql(string type, object value)
        {
            if (value == null)
                return "null";
            return type switch
            {
                "char" or "date" or "text" or "varchar" or "datetime" or "tinytext" => $"'{value}'",
                "int" => $"{(int)(value)}",
                _ => value.ToString(),
            };
            throw new ArgumentNullException();
        }
    }
}
